// active_object.cpp


#include <csignal>
#include <cstdio>
#include <cstdlib>

#include <chrono>
#include <condition_variable>
#include <future>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <vector>


namespace blogs
{


using std::chrono::milliseconds;
using std::condition_variable;
using std::future;
using std::lock_guard;
using std::mutex;
using std::shared_ptr;
using std::string;
using std::thread;
using std::unique_lock;
using std::vector;


class Result
{

  private:

    string
    __v__;


  public:

    Result(string v)
        : __v__(v)
    {
    }


    string
    get_value()
    {
        return __v__;
    }

};


class Active_Object
{

  public:

    virtual
    shared_ptr<Result>
    make_string(int, char)
    = 0;


    virtual
    void
    display_string(string)
    = 0;

};


class Servant
    : public Active_Object
{

  public:

    shared_ptr<Result>
    make_string(int count, char filler)
    {
        for (int i = 0; i < count; ++i)
            std::this_thread::sleep_for(milliseconds(std::rand() % 100));
        return std::make_shared<Result>(string(count, filler));
    }


    void
    display_string(string str)
    {
        std::this_thread::sleep_for(milliseconds(std::rand() % 10));
        std::printf("%s\n", str.c_str());
    }

};


class Method_Request
{

  protected:

    Servant &
    _servant_;


  public:

    Method_Request(Servant & srvnt)
        : _servant_(srvnt)
    {
    }


    virtual
    void
    execute()
    = 0;

};


class Make_String_Request
    : public Method_Request
{

  private:

    int
    __count__;


    char
    __filler__;


    shared_ptr<Result>
    __result__;


    mutex
    __mtx__;


    condition_variable
    __cv__;


  public:

    Make_String_Request(Servant & srvnt, int count, char filler)
        : Method_Request(srvnt), __result__(nullptr), __count__(count), __filler__(filler)
    {}


    void
    execute()
    {
        lock_guard<mutex> lk(__mtx__);
        __result__ = _servant_.make_string(__count__, __filler__);
        __cv__.notify_one();
    }


    shared_ptr<Result>
    get_result()
    {
        unique_lock<mutex> lk(__mtx__);
        __cv__.wait(lk,
                    [this] {
                        return __result__ != nullptr;
                    });
        return __result__;
    }

};


class Display_String_Request
    : public Method_Request
{

  private:

    string
    __message__;


  public:

    Display_String_Request(Servant & srvnt, string message)
        : Method_Request(srvnt), __message__(message)
    {}


    void
    execute()
    {
        _servant_.display_string(__message__);
    }

};


class Activation_Queue
{

  private:

    static
    int const
    __MAX_METHOD_REQUEST__ = 100;


    vector<shared_ptr<Method_Request>>
    __requests__;


    int
    __count__;


    int
    __head__;


    int
    __tail__;


    mutex
    __mtx__;


    condition_variable
    __cv__;


  public:

    Activation_Queue()
        : __requests__(__MAX_METHOD_REQUEST__), __count__(0), __head__(0), __tail__(0)
    {}


    void
    put_request(shared_ptr<Method_Request> request)
    {
        unique_lock<mutex> lk(__mtx__);
        __cv__.wait(lk,
                    [this] {
                        bool full = __count__ == __MAX_METHOD_REQUEST__;
                        if (full)
                            std::printf("Request queue is full. Waiting...\n");
                        return !full;
                    });
        __requests__[__tail__] = request;
        __tail__ = (__tail__ + 1) % __MAX_METHOD_REQUEST__;
        ++__count__;
        __cv__.notify_all();
    }


    shared_ptr<Method_Request>
    take_request()
    {
        unique_lock<mutex> lk(__mtx__);
        __cv__.wait(lk,
                    [this] {
                        bool idle = __count__ == 0;
                        if (idle)
                            std::printf("Request queue is empty. Waiting...\n");
                        return !idle;
                    });
        shared_ptr<Method_Request> request = __requests__[__head__];
        __head__ = (__head__ + 1) % __MAX_METHOD_REQUEST__;
        --__count__;
        __cv__.notify_all();
        return request;
    }

};


class Scheduler
{

  private:

    Activation_Queue
    __queue__;


  public:

    Scheduler()
        : __queue__()
    {}


    shared_ptr<Result>
    invoke(shared_ptr<Method_Request> request)
    {
        __queue__.put_request(request);
        shared_ptr<Result> result;
        Make_String_Request * msr;
        if ((msr = dynamic_cast<Make_String_Request *>(request.get()))) {
            result = shared_ptr<Result>(msr->get_result());
        } else {
            result = nullptr;
        }
        return result;
    }


    void
    run()
    {
        while (true) {
            shared_ptr<Method_Request> request = __queue__.take_request();
            request->execute();
        }
    }

};


class Proxy
    : public Active_Object
{

  private:

    Servant
    __servant__;


    Scheduler
    __scheduler__;


    thread
    __runner__;


  public:

    Proxy()
        : __servant__(), __scheduler__(), __runner__()
    {
        __runner__ = thread(&Scheduler::run, &__scheduler__);
    }


    ~Proxy()
    {
        __runner__.join();
    }


    shared_ptr<Result>
    make_string(int count, char filler)
    {
        shared_ptr<Method_Request> request(new Make_String_Request(__servant__, count, filler));
        future<shared_ptr<Result>> f = std::async(std::launch::async,
                                                  &Scheduler::invoke,
                                                  &__scheduler__,
                                                  request);
        return f.get();
    }


    void
    display_string(string message)
    {
        shared_ptr<Method_Request> request(new Display_String_Request(__servant__, message));
        future<shared_ptr<Result>> f = std::async(std::launch::async,
                                                  &Scheduler::invoke,
                                                  &__scheduler__,
                                                  request);
    }

};


class Active_Object_Factory
{

  public:

    static
    shared_ptr<Active_Object>
    create_active_object()
    {
        return shared_ptr<Active_Object>(new Proxy());
    }

};


class Maker_Client
{

  private:

    string
    __name__;


    Active_Object &
    __ao__;


  public:

    Maker_Client(string name, Active_Object & ao)
        : __name__(name), __ao__(ao)
    {}


    void
    run()
    {
        for (int i = 1; true; ++i) {
            shared_ptr<Result> r = __ao__.make_string(i % 72, __name__[0]);
            std::this_thread::sleep_for(milliseconds(std::rand() % 10));
            std::printf("%s: %s\n", __name__.c_str(), r->get_value().c_str());
        }
    }

};


class Display_Client
{

  private:

    string
    __name__;


    Active_Object &
    __ao__;


  public:

    Display_Client(string name, Active_Object & ao)
        : __name__(name), __ao__(ao)
    {}


    void
    run()
    {
        for (int i = 1; true; ++i) {
            string s(__name__ + ": " + std::to_string(i));
            __ao__.display_string(s);
            std::this_thread::sleep_for(milliseconds(std::rand() % 200));
        }
    }

};


void
bye(int sig = SIGTERM)
{
    std::printf("\nBye Active Object...\n\n");
    std::exit(sig);
}


} // end of namespace blogs


int
main(int argc, char * argv[])
{
    using std::shared_ptr;
    using std::thread;
    using blogs::Active_Object;
    using blogs::Active_Object_Factory;
    using blogs::Display_Client;
    using blogs::Maker_Client;

    std::signal(SIGINT, blogs::bye);
    std::srand(std::time(0));

    shared_ptr<Active_Object> ao = Active_Object_Factory::create_active_object();

    Maker_Client alice("Alice", *ao);
    Maker_Client bobby("Bobby", *ao);
    Display_Client chris("Chris", *ao);

    thread t1(&Maker_Client::run, &alice);
    thread t2(&Maker_Client::run, &bobby);
    thread t3(&Display_Client::run, &chris);
    t1.join();
    t2.join();
    t3.join();

    blogs::bye();
    return 0;
}

